package com.zenika.zenbinding.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.zenika.zenbinding.client.converter.Converter;
import com.zenika.zenbinding.client.exception.DataException;
import com.zenika.zenbinding.client.handler.CheckBoxHandler;
import com.zenika.zenbinding.client.handler.FlexTableHandler;
import com.zenika.zenbinding.client.handler.LabelHandler;
import com.zenika.zenbinding.client.handler.ListBindingHandler;
import com.zenika.zenbinding.client.handler.ListBoxHandler;
import com.zenika.zenbinding.client.handler.RadioListHandler;
import com.zenika.zenbinding.client.handler.TextAreaHandler;
import com.zenika.zenbinding.client.handler.TextBoxHandler;
import com.zenika.zenbinding.client.model.BusinessObject;
import com.zenika.zenbinding.client.model.ListModel;
import com.zenika.zenbinding.client.model.NullValueModel;
import com.zenika.zenbinding.client.model.TableModel;
import com.zenika.zenbinding.client.model.ValueModel;
import com.zenika.zenbinding.client.model.ZenEnum;
import com.zenika.zenbinding.client.renderer.Renderer;
import com.zenika.zenbinding.client.util.BeanUtil;
import com.zenika.zenbinding.client.widget.RadioGroup;

/**
 * This static class allows you to bind your data properties with GWT widgets. There is 3 ways of binding :
 * <ul>
 * <li> The value binding which is the simpliest. It binds the property of an object with a simple widget. The property
 * is declared by a pointed annotation (if you want to bind the street name of an adress you should declare it as
 * "adress.streetname")</li>
 * <li> The List binding which bind the proposed choices of a list with an Array,a List or an Object. It's done in 2
 * steps : the creation of a listBoxHandler (the filling of the choices list) with the createListBoxHandler method and
 * the the creation of a ListModel (binding of the selected object(s) with an object property) with the bindList method.
 * </li>
 * <li> The table binding which binds many property of objects contained in a List or an Array. Properties are declared
 * through a Rendered object.</li>
 * </ul>
 * To be bindable, an object must implement the BusinessObject interface.
 * 
 * @author Zenika
 */
public class ZenBinder {

	/**
	 * <p>
	 * Bind a boolean (primitive or wrapper object) to a CheckBox. The converter is already set to BooleanConverter.
	 * </p>
	 * <p>
	 * Example :
	 * </p>
	 * <p>
	 * The person object has 2 string property: "name" and "firstName" and a boolean property : "alive".
	 * </p>
	 * <code>
	 * Person person = new Person("Robert","Charlebois",true); <br/>
	 * CheckBox checkBox = new CheckBox(); <br/>
	 * ZenBinder.bindValue(checkBox, person, "alive"); <br/>
	 * </code>
	 * 
	 * @param checkBox
	 *            The CheckBox to be bound
	 * @param object
	 *            The object containing the property to bind
	 * @param property
	 *            The path of the property
	 * @return The ValueModel on which you can specify validators and a feedback manager.
	 */
	public static ValueModel bindValue(CheckBox checkBox, BusinessObject object, String property) {
		ValueModel model = new ValueModel(object, property);
		model.setConverter(BeanUtil.converters.BOOLEAN);
		CheckBoxHandler handler = new CheckBoxHandler(checkBox, model);
		handler.addValueBindingListener(model);
		return model;
	}

	/**
	 * <p>
	 * Bind a boolean (primitive or wrapper object) to a Label.
	 * </p>
	 * <p>
	 * Example :
	 * </p>
	 * <p>
	 * The person object has 2 string property: "name" and "firstName" and a boolean property : "alive".
	 * </p>
	 * <code>
	 * Person person = new Person("Robert","Charlebois",true); <br/>
	 * Label label = new Label(); <br/>
	 * ZenBinder.bindValue(label, person, "name"); <br/>
	 * </code>
	 * 
	 * @param label
	 *            The Label to be bound
	 * @param object
	 *            The object containing the property to bind
	 * @param property
	 *            The path of the property
	 * 
	 * @return The ValueModel on which you can specify validators and a feedback manager.
	 */
	public static ValueModel bindValue(Label label, BusinessObject object, String property) {
		ValueModel model = new ValueModel(object, property);
		try {
			model.setConverter(BeanUtil.getDefaultConverter(object, property));
		} catch (DataException e) {
			e.printStackTrace();
		}
		LabelHandler handler = new LabelHandler(label, model);
		handler.addValueBindingListener(model);
		return model;
	}

	/**
	 * <p>
	 * Bind a property of an object to a TextBox. You can specify the converter of the returned value model but it's
	 * automatically chosen by the ZenBinder.
	 * </p>
	 * <p>
	 * Example :
	 * </p>
	 * <p>
	 * The person object has 2 string property: "name" and "fisrtName" and a boolean property : "alive".
	 * </p>
	 * <code>
	 * Person person = new Person("Robert","Charlebois",true); <br/>
	 * TextBox testBox = new TextBox(); <br/>
	 * ZenBinder.bindValue(textBox, person, "name"); <br/>
	 * </code>
	 * 
	 * @param textBox
	 *            The TextBox to be bound
	 * @param object
	 *            The object containing the property to bind
	 * @param property
	 *            The path of the property
	 * @return The ValueModel on which you can specify validators and a feedback manager.
	 */
	public static ValueModel bindValue(TextBox textBox, BusinessObject object, String property) {
		ValueModel model = new ValueModel(object, property);
		try {
			model.setConverter(BeanUtil.getDefaultConverter(object, property));
		} catch (DataException e) {
			e.printStackTrace();
		}
		TextBoxHandler handler = new TextBoxHandler(textBox, model);
		handler.addValueBindingListener(model);
		return model;
	}

	/**
	 * <p>
	 * Bind a property of an object to a TextBox. You can specify the converter of the returned value model but it's
	 * automatically chosen by the ZenBinder.
	 * </p>
	 * <p>
	 * Example :
	 * </p>
	 * <p>
	 * The person object has 2 string property: "name" and "fisrtName" and a boolean property : "alive".
	 * </p>
	 * <code>
	 * Person person = new Person("Robert","Charlebois",true); <br/>
	 * TextBox testBox = new TextBox(); <br/>
	 * ZenBinder.bindValue(textBox, person, "name"); <br/>
	 * </code>
	 * 
	 * @param textBox
	 *            The TextBox to be bound
	 * @param object
	 *            The object containing the property to bind
	 * @param property
	 *            The path of the property
	 * @return The ValueModel on which you can specify validators and a feedback manager.
	 */
	public static ValueModel bindValue(TextBox textBox, BusinessObject object, String property, Converter converter) {
		ValueModel model = new ValueModel(object, property);
		try {
			if (converter == null)
				model.setConverter(BeanUtil.getDefaultConverter(object, property));
			else
				model.setConverter(converter);
		} catch (DataException e) {
			e.printStackTrace();
		}
		TextBoxHandler handler = new TextBoxHandler(textBox, model);
		handler.addValueBindingListener(model);
		return model;
	}

	/**
	 * <p>
	 * Bind a property of an object to a TextArea. You can specify the converter of the returned value model but it's
	 * automatically chosen by the ZenBinder.
	 * </p>
	 * <p>
	 * Example :
	 * </p>
	 * <p>
	 * The person object has 2 string property: "name" and "fisrtName" and a boolean property : "alive".
	 * </p>
	 * <code>
	 * Person person = new Person("Robert","Charlebois",true); <br/>
	 * TextArea testArea = new TextArea(); <br/>
	 * ZenBinder.bindValue(textArea, person, "name"); <br/>
	 * </code>
	 * 
	 * @param textBox
	 *            The TextArea to be bound
	 * @param object
	 *            The object containing the property to bind
	 * @param property
	 *            The path of the property
	 * @return The ValueModel on which you can specify validators and a feedback manager.
	 */
	public static ValueModel bindValue(TextArea textArea, BusinessObject object, String property) {
		ValueModel model = new ValueModel(object, property);
		try {
			model.setConverter(BeanUtil.getDefaultConverter(object, property));
		} catch (DataException e) {
			e.printStackTrace();
		}
		TextAreaHandler handler = new TextAreaHandler(textArea, model);
		handler.addValueBindingListener(model);
		return model;
	}

	/**
	 * <p>
	 * Create a ListModel and bind it to a listbox by using a listbox handler. The property pointed by the list model
	 * can be an Array, a List or an Object.
	 * </p>
	 * Example : <br>
	 * You have an array list filled with City objects. You can do : <br>
	 * <code>
	 * Person person = new Person(); <br>
	 * ListBox listBox = new ListBox(); <br>
	 * ListBoxHandler handler = ZenBinder.createListBoxHandler(listBox, cityList, "name"); <br>
	 * ZenBinder.bindList(handler, person, "address.city"); <br>
	 * </code>
	 * 
	 * @param handler
	 *            The handler of the widget you want to bind
	 * @param object
	 *            The object to bind
	 * @param property
	 *            The path of the object property
	 * @return The ListModel on which you can add validator and set a feedback manager
	 */
	public static ListModel bindList(ListBindingHandler handler, BusinessObject object, String property) {
		ListModel model = bindList(handler, object, property, null);
		return model;
	}

	public static ListModel bindList(ListBindingHandler handler, BusinessObject object, String property,
			Converter converter) {
		ListModel model = new ListModel(object, property, converter);
		handler.setListModel(model);
		return model;
	}

	public static ListModel bindListBox(ListBox listBox, BusinessObject object, String property,
			BusinessObject[] objects, String propertyInArray) {
		ListBindingHandler handler = createListBindingHandler(listBox, objects, propertyInArray);
		ListModel listModel = bindList(handler, object, property);
		return listModel;
	}

	public static ListModel bindListBox(ListBox listBox, BusinessObject object, String property, Object[] choices) {
		ListBindingHandler handler = createListBindingHandler(listBox, choices);
		ListModel listModel = bindList(handler, object, property);
		return listModel;
	}

	public static ListModel bindListBox(ListBox listBox, BusinessObject object, String property, List objects,
			String propertyInList) {

		BusinessObject[] businessObjectArray = (BusinessObject[]) objects.toArray(new BusinessObject[objects.size()]);
		return bindListBox(listBox, object, property, businessObjectArray, propertyInList);
	}

	/**
	 * Create a list binding handler used to bind a list.
	 * 
	 * @param listBox
	 *            The listbox to bind
	 * @param objects
	 *            An array of objects to show in the listbox
	 * @param property
	 *            The property of the object to show
	 * @return The ListBoxHandler ready to bind with a ListModel
	 */
	public static ListBindingHandler createListBindingHandler(ListBox listBox, BusinessObject[] objects, String property) {
		try {
			List modelList = new ArrayList();
			for (int i = 0; i < objects.length; i++) {
				if (objects[i] != null) {
					ValueModel model = new ValueModel(objects[i], property);
					model.setConverter(BeanUtil.getDefaultConverter(objects[i], property));
					modelList.add(model);

				} else {
					NullValueModel model = new NullValueModel();
					modelList.add(model);
				}
			}
			ListBoxHandler handler = new ListBoxHandler(listBox, modelList);
			return handler;
		} catch (DataException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Create a list binding handler used to bind a list.
	 * 
	 * @param listBox
	 *            The listbox to bind
	 * @param objects
	 *            A List containing objects to show in the listbox
	 * @param property
	 *            The property of the object to show
	 * @return The ListBoxHandler ready to bind with a ListModel
	 */
	public static ListBindingHandler createListBindingHandler(ListBox listBox, List objects, String property) {
		Object[] objs = objects.toArray();
		BusinessObject[] bo = new BusinessObject[objs.length];
		for (int i = 0; i < objs.length; i++) {
			bo[i] = (BusinessObject) objs[i];
		}
		return createListBindingHandler(listBox, bo, property);
	}

	public static ListBindingHandler createListBindingHandler(ListBox listBox, ZenEnum[] objects) {
		ArrayList valueList = new ArrayList();
		for (int i = 0; i < objects.length; i++) {
			ValueModel model = new ValueModel(objects[i].getValue());
			try {
				model.setConverter(BeanUtil.getDefaultConverter(objects[i].getValue()));
			} catch (DataException e) {
				e.printStackTrace();
			}
			valueList.add(model);
		}
		ListBindingHandler handler = new ListBoxHandler(listBox, valueList);
		return handler;
	}

	public static ListBindingHandler createListBindingHandler(ListBox listBox, Object[] choices) {
		ArrayList valueList = new ArrayList();
		for (int i = 0; i < choices.length; i++) {
			ValueModel model = new ValueModel(choices[i]);
			try {
				model.setConverter(BeanUtil.getDefaultConverter(choices[i]));
			} catch (DataException e) {
				e.printStackTrace();
			}
			valueList.add(model);
		}
		ListBindingHandler handler = new ListBoxHandler(listBox, valueList);
		return handler;
	}

	public static ListBindingHandler createListBindingHandler(RadioGroup radioGroup, List businessObjects,
			String property) {
		Object[] objects = businessObjects.toArray();
		BusinessObject[] bo = new BusinessObject[objects.length];
		for (int i = 0; i < bo.length; i++) {
			bo[i] = (BusinessObject) objects[i];
		}
		return createListBindingHandler(radioGroup, bo, property);
	}

	public static ListBindingHandler createListBindingHandler(RadioGroup radioGroup, BusinessObject[] businessObjects,
			String property) {
		ArrayList valueList = new ArrayList();
		for (int i = 0; i < businessObjects.length; i++) {
			ValueModel model = new ValueModel(businessObjects[i], property);
			try {
				model.setConverter(BeanUtil.getDefaultConverter(businessObjects[i], property));
			} catch (DataException e) {
				e.printStackTrace();
			}
			valueList.add(model);
		}
		ListBindingHandler handler = new RadioListHandler(radioGroup, valueList);
		return handler;
	}

	public static ListBindingHandler createListBindingHandler(RadioGroup radioGroup, Object[] objects) {
		ArrayList valueList = new ArrayList();
		for (int i = 0; i < objects.length; i++) {
			ValueModel model = new ValueModel(objects[i]);
			try {
				model.setConverter(BeanUtil.getDefaultConverter(objects[i]));
			} catch (DataException e) {
				e.printStackTrace();
			}
			valueList.add(model);
		}
		ListBindingHandler handler = new RadioListHandler(radioGroup, valueList);
		return handler;
	}

	public static ListBindingHandler createListBindingHandler(RadioGroup radioGroup, List objects) {
		Object[] objs = objects.toArray();
		return createListBindingHandler(radioGroup, objs);
	}

	/**
	 * <p>
	 * Bind a FlewTable with many objects. Each objects represent a row of the table and each column is displayed by a
	 * Renderer object with generate the view in function of a property of the object of the row.
	 * </p>
	 * Example : <br>
	 * <code>
	 * FlexTable table = new FlexTable(); <br>
	 * BusinessObject[] sources = { person, person2 }; <br>
	 * Renderer[] renderers = { new LabelRenderer("firstName"), new LabelRenderer("name"), new
	 * TextBoxRenderer("address.streetName") }; <br>
	 * Label[] headers = {new Label("First Name"), new Label("Name"), new Label("Street")}; <br>
	 * ZenBinder.bindTable(table, sources, headers, renderers); <br>
	 * </code>
	 * 
	 * @param table
	 *            The FlexTable to bind
	 * @param objects
	 *            Array containing object to bind
	 * @param headers
	 *            Array of Label objects which will be the table headers
	 * @param renderers
	 *            Array of Renderer objects. Each Renderer is a column.
	 * @return The TableModel on which you can set feedback manager and validators.
	 */
	public static TableModel bindTable(FlexTable table, BusinessObject[] objects, Label[] headers, Renderer[] renderers) {
		TableModel model = new TableModel(objects, headers, renderers);
		new FlexTableHandler(table, model);
		return model;
	}

	/**
	 * <p>
	 * Bind a FlewTable with many objects. Each objects represent a row of the table and each column is displayed by a
	 * Renderer object with generate the view in function of a property of the object of the row.
	 * </p>
	 * Example : <br>
	 * <code>
	 * FlexTable table = new FlexTable(); <br>
	 * BusinessObject[] sources = { person, person2 }; <br>
	 * Renderer[] renderers = { new LabelRenderer("firstName"), new LabelRenderer("name"), new
	 * TextBoxRenderer("address.streetName") }; <br>
	 * String[] headers = {"First Name", "Name", "Street"}; <br>
	 * ZenBinder.bindTable(table, sources, headers, renderers); <br>
	 * </code>
	 * 
	 * @param table
	 *            The FlexTable to bind
	 * @param objects
	 *            Array containing object to bind
	 * @param headers
	 *            headers names
	 * @param renderers
	 *            Array of Renderer objects. Each Renderer is a column.
	 * @return The TableModel on which you can set feedback manager and validators.
	 */
	public static TableModel bindTable(FlexTable table, BusinessObject[] objects, String[] headers, Renderer[] renderers) {
		Label[] labels = new Label[headers.length];
		for (int i = 0; i < headers.length; i++) {
			labels[i] = new Label(headers[i]);
		}
		return bindTable(table, objects, labels, renderers);
	}

	/**
	 * <p>
	 * Bind a FlewTable with many objects. Each objects represent a row of the table and each column is displayed by a
	 * Renderer object with generate the view in function of a property of the object of the row.
	 * </p>
	 * Example : <br>
	 * <code>
	 * FlexTable table = new FlexTable(); <br>
	 * ArrayList sources = new ArrayList(); <br>
	 * sources.add(person); <br>
	 * sources.add(person2); <br>
	 * Renderer[] renderers = { new LabelRenderer("firstName"), new LabelRenderer("name"), new
	 * TextBoxRenderer("address.streetName") }; <br>
	 * Label[] headers = {new Label("First Name"), new Label("Name"), new Label("Street")}; <br>
	 * ZenBinder.bindTable(table, sources, headers, renderers); <br>
	 * </code>
	 * 
	 * @param table
	 *            The FlexTable to bind
	 * @param objects
	 *            List containing object to bind
	 * @param headers
	 *            Array of Label objects which will be the table headers
	 * @param renderers
	 *            Array of Renderer objects. Each Renderer is a column.
	 * @return The TableModel on which you can set feedback manager and validators.
	 */
	public static TableModel bindTable(FlexTable table, List objects, Label[] headers, Renderer[] renderers) {
		Object[] objs = objects.toArray();
		BusinessObject[] bo = new BusinessObject[objs.length];
		for (int i = 0; i < objs.length; i++) {
			bo[i] = (BusinessObject) objs[i];
		}
		return bindTable(table, bo, headers, renderers);
	}

	/**
	 * <p>
	 * Bind a FlewTable with many objects. Each objects represent a row of the table and each column is displayed by a
	 * Renderer object with generate the view in function of a property of the object of the row.
	 * </p>
	 * Example : <br>
	 * <code>
	 * FlexTable table = new FlexTable(); <br>
	 * ArrayList sources = new ArrayList(); <br>
	 * sources.add(person); <br>
	 * sources.add(person2); <br>
	 * Renderer[] renderers = { new LabelRenderer("firstName"), new LabelRenderer("name"), new
	 * TextBoxRenderer("address.streetName") }; <br>
	 * String[] headers = {"First Name", "Name", "Street"}; <br>
	 * ZenBinder.bindTable(table, sources, headers, renderers); <br>
	 * </code>
	 * 
	 * @param table
	 *            The FlexTable to bind
	 * @param objects
	 *            List containing objects to bind
	 * @param headers
	 *            Array of Label objects which will be the table headers
	 * @param renderers
	 *            Array of Renderer objects. Each Renderer is a column.
	 * @return The TableModel on which you can set feedback manager and validators.
	 */
	public static TableModel bindTable(FlexTable table, List objects, String[] headers, Renderer[] renderers) {
		Label[] labels = new Label[headers.length];
		for (int i = 0; i < headers.length; i++) {
			labels[i] = new Label(headers[i]);
		}
		return bindTable(table, objects, labels, renderers);
	}

}
